/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "hmfs_zoned.h"

#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#ifdef HAVE_LINUX_LIMITS_H
#include <linux/limits.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#include <sys/stat.h>
#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif
#include <unistd.h>
#include <filesystem>
#include <fstream>
#include <sstream>

#include "securec.h"
#include "config.h"
#include "hmfs_utils.h"


#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif


namespace OHOS::Hmfs {

extern hmfs_configuration g_hmfsConfig;

HmfsZoned& HmfsZoned::GetInstance()
{
    static HmfsZoned instance;
    return instance;
}

#ifdef HAVE_LINUX_BLKZONED_H

int HmfsZoned::GetSysFsPath(std::string& devPath, const std::string& attr, std::string& filePath)
{
    struct stat statbuf;
    if (stat(devPath.c_str(), &statbuf) < 0) {
        return -1;
    }

    int32_t majorNum = major(statbuf.st_rdev);
    int32_t minorNum = minor(statbuf.st_rdev);
    std::string sysBlockPath = "/sys/dev/block/" + std::to_string(majorNum) + ":" + std::to_string(minorNum);

    char linkBuf[PATH_MAX];
    ssize_t len = readlink(sysBlockPath.c_str(), linkBuf, sizeof(linkBuf) - 1);
    if (len < 0) {
        return -1;
    }
    linkBuf[len] = '\0';
    std::string resolvedPath(linkBuf);

    std::string sysfsPath = "/sys/dev/block/" + resolvedPath;
    std::string partitionPath = sysfsPath + "/partition";
    if (std::filesystem::exists(partitionPath)) {
        auto lastSlash = sysfsPath.find_last_of('/');
        if (lastSlash == std::string::npos) {
            return -1;
        }
        sysfsPath = sysfsPath.substr(0, lastSlash);
    }

    filePath = sysfsPath + "/" + attr;
    return 0;
}

int HmfsZoned::HmfsGetZonedModel(DeviceInfo *dev)
{
    /* Check that this is a zoned block device */
    std::string filePath;
    int res = GetSysFsPath(dev->path, "queue/zoned", filePath);
    if (res != 0) {
        HMFS_INFO("can't find /sys, assuming normal block device.");
        dev->zonedModel = HMFS_ZONED_NONE;
        return 0;
    }

    std::ifstream file(filePath, std::ifstream::in);
    if (!file) {
        dev->zonedModel = HMFS_ZONED_NONE;
        return 0;
    }
    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string content = buffer.str();
    content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
    if (content == "none") {
        /* Regular block device */
        dev->zonedModel = HMFS_ZONED_NONE;
    } else if (content == "host-aware") {
        /* Host-aware zoned block device: can be randomly written */
        dev->zonedModel = HMFS_ZONED_HA;
    } else if (content == "host-managed") {
        /* Host-managed zoned block device: sequential writes needed */
        dev->zonedModel = HMFS_ZONED_HM;
    } else {
        HMFS_ERROR("Unsupported device zoned model");
        return -1;
    }

    return 0;
}

uint32_t HmfsZoned::HmfsGetZoneChunkSectors(DeviceInfo *dev)
{
    std::string filePath;
    int res = GetSysFsPath(dev->path, "queue/chunk_sectors", filePath);
    if (res != 0) {
        HMFS_ERROR("\tError: Failed to get device sysfs attribute path\n");
        return 0;
    }

    std::ifstream file(filePath, std::ifstream::in);
    if (!file) {
        return 0;
    }
    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string content = buffer.str();
    content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
    uint32_t sectors = atoi(content.c_str());
    return sectors;
}

int HmfsZoned::HmfsGetZoneBlocks(DeviceInfo *dev)
{
    /* Get zone size */
    dev->zoneBlocks = 0;

    uint64_t sectors = HmfsGetZoneChunkSectors(dev);
    if (sectors == 0) {
        return -1;
    }

    dev->zoneSize = sectors << SECTOR_SHIFT;
    dev->zoneBlocks = sectors >> (HMFS_BLKSIZE_BITS - SECTOR_SHIFT);
    sectors = dev->zoneSize / dev->sectorSize;

    /*
    * Total number of zones: there may
    * be a last smaller runt zone.
    */
    dev->nrZones = dev->sectorCount / sectors;
    if (dev->sectorCount % sectors) {
        dev->nrZones++;
    }

    return 0;
}

int HmfsZoned::HmfsReportZone(int i, uint64_t sector, BlockZone *blkzone)
{
    int ret = -1;
    auto report = std::make_unique<OneZoneReport>();
    OneZoneReport *rep = report.get();
    if (!report) {
        HMFS_ERROR("No memory for report zones\n");
        return -ENOMEM;
    }
    memset_s(rep, sizeof(OneZoneReport), 0, sizeof(OneZoneReport));
    rep->rep.sector = sector;
    rep->rep.nrZones = 1;

    ret = ioctl(g_hmfsConfig.devices[i].fd, BLKREPORTZONE, rep);
    if (ret != 0) {
        ret = -errno;
        HMFS_ERROR("ioctl BLKREPORTZONE failed: errno=%d\n", errno);
        return ret;
    }

    *blkzone = rep->zone;
    return ret;
}

int HmfsZoned::HmfsReportZones(int j, ReportZonesCb *reportZonesCb, void *opaque)
{
    StructDeviceInfo *dev = g_hmfsConfig.devices + j;
    BlockZone *blkz = nullptr;
    unsigned int n = 0;
    uint64_t totalSectors = (dev->totalSectors * g_hmfsConfig.sectorSize) >> SECTOR_SHIFT;
    uint64_t sector = 0;
    int ret = -1;

    auto report = std::make_unique<uint8_t[]>(HMFS_REPORT_ZONES_BUFSZ);
    BlockZoneReport *rep = reinterpret_cast<BlockZoneReport *>(report.get());
    if (rep == nullptr) {
        HMFS_ERROR("No memory for report zones\n");
        return -ENOMEM;
    }

    while (sector < totalSectors) {
        /* Get zone info */
        rep->sector = sector;
        rep->nrZones = (HMFS_REPORT_ZONES_BUFSZ - sizeof(BlockZoneReport)) / sizeof(BlockZone);

        ret = ioctl(dev->fd, BLKREPORTZONE, rep);
        if (ret != 0) {
            ret = -errno;
            HMFS_ERROR("ioctl BLKREPORTZONE failed: errno=%d\n", errno);
            return ret;
        }

        if (rep->nrZones == 0) {
            ret = -EIO;
            HMFS_ERROR("Unexpected ioctl BLKREPORTZONE result\n");
            return ret;
        }

        blkz = reinterpret_cast<BlockZone *>(rep + 1);
        for (unsigned int i = 0; i < rep->nrZones; i++) {
            ret = reportZonesCb(n, blkz, opaque);
            if (ret != 0) {
                return ret;
            }
            sector = BLK_ZONE_SECTOR(blkz) + BLK_ZONE_LENGTH(blkz);
            n++;
            blkz++;
        }
    }
    return ret;
}

int HmfsZoned::HmfsCheckZonesPart(DeviceInfo *dev, BlockZoneReport *rep, 
    uint64_t &sector, uint64_t &totalSectors, unsigned int &n)
{
    int lastIsConv = 1;
    BlockZone *blkz = nullptr;
    while (sector < totalSectors) {
        /* Get zone info */
        memset_s(rep, HMFS_REPORT_ZONES_BUFSZ, 0, HMFS_REPORT_ZONES_BUFSZ);
        rep->sector = sector;
        rep->nrZones = (HMFS_REPORT_ZONES_BUFSZ - sizeof(BlockZoneReport)) / sizeof(BlockZone);

        int ret = ioctl(dev->fd, BLKREPORTZONE, rep);
        if (ret != 0) {
            ret = -errno;
            return ret;
        }

        if (rep->nrZones == 0) {
            break;
        }

        blkz = reinterpret_cast<BlockZone *>(rep + 1);
        for (unsigned int i = 0; i < rep->nrZones && sector < totalSectors; i++) {
            if (BLK_ZONE_COND(blkz) == BLK_ZONE_COND_READONLY || BLK_ZONE_COND(blkz) == BLK_ZONE_COND_OFFLINE) {
                lastIsConv = 0;
            }

            if (BLK_ZONE_CONV(blkz) || BLK_ZONE_SEQ_PREF(blkz)) {
                if (lastIsConv) {
                    dev->nrRndZones++;
                }
            } else {
                lastIsConv = 0;
            }

            if (BLK_ZONE_CONV(blkz)) {
                HMFS_MINOR_DEBUG("Zone %05u: Conventional, cond 0x%x (%s), sector %llu, %llu sectors\n",
                    n, BLK_ZONE_COND(blkz), BlkZoneCondStr(blkz), BLK_ZONE_SECTOR(blkz), BLK_ZONE_LENGTH(blkz));
                dev->zoneCapBlocks[n] = BLK_ZONE_LENGTH(blkz) >> (HMFS_BLKSIZE_BITS - SECTOR_SHIFT);
            } else {
                HMFS_MINOR_DEBUG("Zone %05u: type 0x%x (%s), cond 0x%x (%s), need_reset %d, nonSeq %d, sector %llu,"
                    " %llu sectors, capacity %llu, wp sector %llu\n",
                    n, BLK_ZONE_TYPE(blkz), BlkZoneTypeStr(blkz), BLK_ZONE_COND(blkz),
                    BlkZoneCondStr(blkz), BLK_ZONE_NEED_RESET(blkz), BLK_ZONE_NON_SEQ(blkz),
                    BLK_ZONE_SECTOR(blkz), BLK_ZONE_LENGTH(blkz), BLK_ZONE_CAPACITY(blkz, rep->flags),
                    BLK_ZONE_WP_SECTOR(blkz));
                dev->zoneCapBlocks[n] = BLK_ZONE_CAPACITY(blkz, rep->flags) >> (HMFS_BLKSIZE_BITS - SECTOR_SHIFT);
            }

            sector = BLK_ZONE_SECTOR(blkz) + BLK_ZONE_LENGTH(blkz);
            n++;
            blkz++;
        }
    }
    return 0;
}

int HmfsZoned::HmfsCheckZones(DeviceInfo *dev)
{
    uint64_t sector = 0;
    unsigned int n = 0;
    int ret = -1;

    auto report = std::make_unique<uint8_t[]>(HMFS_REPORT_ZONES_BUFSZ);
    BlockZoneReport *rep = reinterpret_cast<BlockZoneReport *>(report.get());
    if (rep == nullptr) {
        HMFS_ERROR("No memory for report zones\n");
        return -ENOMEM;
    }

    dev->zoneCapBlocks = std::make_unique<size_t[]>(dev->nrZones);
    if (!dev->zoneCapBlocks) {
        HMFS_ERROR("No memory for zone capacity list.\n");
        return -ENOMEM;
    }
    memset_s(dev->zoneCapBlocks.get(), (dev->nrZones * sizeof(size_t)), 0, (dev->nrZones * sizeof(size_t)));

    dev->nrRndZones = 0;

    uint64_t totalSectors = (dev->sectorCount * g_hmfsConfig.sectorSize) >> 9;
    ret = HmfsCheckZonesPart(dev, rep, sector, totalSectors, n);
    if (ret < 0) {
        HMFS_ERROR("ioctl BLKREPORTZONE failed\n");
        return ret;
    }

    if (sector != totalSectors) {
        HMFS_ERROR("Invalid zones: last sector reported is %llu, expected %llu\n",
            (unsigned long long)(sector << 9) / g_hmfsConfig.sectorSize,
            (unsigned long long)dev->sectorCount);
        ret = -1;
        return ret;
    }

    if (n != dev->nrZones) {
        HMFS_ERROR("Inconsistent number of zones: expected %u zones, got %u\n", dev->nrZones, n);
        ret = -1;
        return ret;
    }

    /*
    * For a multi-device volume, fixed position metadata blocks are
    * stored * only on the first device of the volume. Checking for the
    * presence of * conventional zones (randomly writeabl zones) for
    * storing these blocks * on a host-managed device is thus needed only
    * for the device index 0.
    */
    if (dev->isMetaDevice && dev->zonedModel == HMFS_ZONED_HM && dev->nrRndZones == 0) {
        HMFS_ERROR("No conventional zone for super block\n");
        ret = -1;
    }
    return ret;
}

int HmfsZoned::HmfsResetZone(int i, void *blkzone)
{
    BlockZone *blkz = static_cast<BlockZone *>(blkzone);
    StructDeviceInfo *dev = g_hmfsConfig.devices + i;
    blk_zone_range range;
    int ret;

    if (!BLK_ZONE_SEQ(blkz) || BLK_ZONE_EMPTY(blkz)) {
        return 0;
    }

    /* Non empty sequential zone: reset */
    range.sector = BLK_ZONE_SECTOR(blkz);
    range.nr_sectors = BLK_ZONE_LENGTH(blkz);
    ret = ioctl(dev->fd, BLKRESETZONE, &range);
    if (ret != 0) {
        ret = -errno;
        HMFS_ERROR("ioctl BLKRESETZONE failed: errno=%d\n", errno);
    }

    return ret;
}

int HmfsZoned::HmfsResetZones(DeviceInfo *deviceInfo)
{
    auto buf = std::make_unique<uint8_t[]>(HMFS_REPORT_ZONES_BUFSZ);
    if (buf == nullptr) {
        HMFS_ERROR("Not enough memory for reset zones.");
        return -1;
    }

    BlockZoneReport* report = reinterpret_cast<BlockZoneReport*>(buf.get());
    uint64_t sector = 0;
    uint64_t sectorCount = (deviceInfo->sectorCount * deviceInfo->sectorSize) / DEFAULT_SECTOR_SIZE;
    while (sector < sectorCount) {
        /* Get zone info */
        memset_s(report, HMFS_REPORT_ZONES_BUFSZ, 0, HMFS_REPORT_ZONES_BUFSZ);
        report->sector = sector;
        report->nrZones = (HMFS_REPORT_ZONES_BUFSZ - sizeof(BlockZoneReport)) / sizeof(BlockZone);
        int32_t ret = ioctl(deviceInfo->fd, BLKREPORTZONE, report);
        if (ret != 0) {
            HMFS_ERROR("ioctl BLKREPORTZONES failed");
            return -1;
        }

        if (report->nrZones == 0) {
            break;
        }

        BlockZone* blockZone = reinterpret_cast<BlockZone*>(report + 1);
        for (uint32_t i = 0; i < report->nrZones && sector < sectorCount; i++) {
            if (BLK_ZONE_SEQ(blockZone) && !BLK_ZONE_EMPTY(blockZone)) {
                /* Non empty sequential zone: reset */
                struct blk_zone_range range;
                range.sector = BLK_ZONE_SECTOR(blockZone);
                range.nr_sectors = BLK_ZONE_LENGTH(blockZone);
                ret = ioctl(deviceInfo->fd, BLKRESETZONE, &range);
                if (ret != 0) {
                    HMFS_ERROR("Failed to perform ioctl BLKREPORTZONES");
                    return -1;
                }
            }
            sector = BLK_ZONE_SECTOR(blockZone) + BLK_ZONE_LENGTH(blockZone);
            blockZone++;
        }
    }

    HMFS_INFO("Discarded %" PRIu64 " MB", (sector * DEFAULT_SECTOR_SIZE) / (1024 * 1024));
    return 0;
}

uint32_t HmfsZoned::HmfsGetUsableSegments(SuperBlockData *superBlock)
{
#ifdef HAVE_BLK_ZONE_REP_V2
    CmdConfig& cfgPara = CmdParser::GetSingleton()->GetCmdConfig();
    if (cfgPara.func == RESIZE) {
        return GetLeValue(superBlock->segmentCountInMain);
    }

    uint32_t usableSegs = 0;
    for (uint32_t i = 0; i < DeviceManager::GetInstance().GetDeviceCount(); i++) {
        DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(i);
        ASSERT(deviceInfo != nullptr);

        if (deviceInfo->zonedModel <= HMFS_ZONED_HM) {
            usableSegs += deviceInfo->segmentCount;
            continue;
        }
        for (uint32_t j = 0; j < deviceInfo->nrZones; j++) {
            usableSegs += AlignUpCount(deviceInfo->zoneCapBlocks[j], BLOCKS_PER_SEGMENT);
        }
    }
    usableSegs -= (GetLeValue(superBlock->mainBlkId) - GetLeValue(superBlock->segment0BlkId)) >>
        GetLeValue(superBlock->logBlksPerSeg);
    return usableSegs;
#else
    return GetLeValue(superBlock->segmentCountInMain);
#endif
}


const char *HmfsZoned::BlkZoneTypeStr(BlockZone *blkz)
{
    switch (BLK_ZONE_TYPE(blkz)) {
        case BLK_ZONE_TYPE_CONVENTIONAL:
            return "Conventional";
        case BLK_ZONE_TYPE_SEQWRITE_REQ:
            return "Sequential-write-required";
        case BLK_ZONE_TYPE_SEQWRITE_PREF:
            return "Sequential-write-preferred";
        default:
            return "Unknown-type";
    }
}

const char *HmfsZoned::BlkZoneCondStr(BlockZone *blkz)
{
    switch (BLK_ZONE_COND(blkz)) {
        case BLK_ZONE_COND_NOT_WP:
            return "Not-write-pointer";
        case BLK_ZONE_COND_EMPTY:
            return "Empty";
        case BLK_ZONE_COND_IMP_OPEN:
            return "Implicit-open";
        case BLK_ZONE_COND_EXP_OPEN:
            return "Explicit-open";
        case BLK_ZONE_COND_CLOSED:
            return "Closed";
        case BLK_ZONE_COND_READONLY:
            return "Read-only";
        case BLK_ZONE_COND_FULL:
            return "Full";
        case BLK_ZONE_COND_OFFLINE:
            return "Offline";
        default:
            return "Unknown-cond";
    }
}

#else

int HmfsZoned::HmfsReportZone(int i, uint64_t sector, BlockZone *blkzone)
{
    HMFS_ERROR("%d: Unsupported zoned block device\n", i);
    return -1;
}

int HmfsZoned::HmfsReportZones(int i, ReportZonesCb *reportZonesCb, void *opaque)
{
    HMFS_ERROR("%d: Unsupported zoned block device\n", i);
    return -1;
}

int HmfsZoned::HmfsGetZonedModel(DeviceInfo* dev)
{
    // StructDeviceInfo *dev = g_hmfsConfig.devices + i;

    g_hmfsConfig.zonedMode = 0;
    dev->zonedModel = HMFS_ZONED_NONE;
    return 0;
}

int HmfsZoned::HmfsGetZoneBlocks(DeviceInfo* dev)
{
    // StructDeviceInfo *dev = g_hmfsConfig.devices + i;

    g_hmfsConfig.zonedMode = 0;
    dev->nrZones = 0;
    dev->zoneBlocks = 0;
    dev->zonedModel = HMFS_ZONED_NONE;

    return 0;
}

int HmfsZoned::HmfsCheckZones(DeviceInfo* dev)
{
    HMFS_ERROR("Unsupported zoned block device\n");
    return -1;
}

int HmfsZoned::HmfsResetZone(int i, void *blkzone)
{
    HMFS_ERROR("%d: Unsupported zoned block device\n", i);
    return -1;
}

int HmfsZoned::HmfsResetZones(DeviceInfo *deviceInfo)
{
    HMFS_ERROR("%d: Unsupported zoned block device\n", i);
    return -1;
}

uint32_t HmfsZoned::HmfsGetUsableSegments(SuperBlockData *sb)
{
    return GetLeValue(sb->segmentCountInMain);
}
#endif
}